﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WordNet.Core.ElementContracts;
using WordNet.Core.Relations;

namespace WordNet.Core
{
    public class SemanticRelationTracer:ISemanticRelations
    {
        IWNDictionary _dictionary;
      
        public SemanticRelationTracer(IWNDictionary dic)
        {
            _dictionary = dic;
        }
        
        public ISenseNode FindRelation(IWordID id, Pointer pointer)
        {
            if (id == null || pointer == null)
                throw new ArgumentNullException("parameters cannot be null");

            if (pointer.Relation != RelationType.Semantic)
                throw new ArgumentException("It is not semantic pointer");

            if (!IsAcceptedPOS(pointer, id.PartOfSpeech))
                return null;
           
            ISynset synset = _dictionary.GetSynset(id.SynsetID);
            return FindRelation(synset, pointer);
        }

        public ISenseNode FindRelation(ISynset synset, Pointer pointer)
        {
            if (synset == null || pointer == null)
                throw new ArgumentNullException("parameters cannot be null");

            if (pointer.Relation != RelationType.Semantic)
                throw new ArgumentException("It is not semantic pointer");

            if (!IsAcceptedPOS(pointer, synset.PartOfSpeech))
                return null;

            List<ISynsetID> children = synset.GetRelatedSynsets(pointer);
            if (children == null)
                return null;
           
            ISenseNode root = new SenseNode(synset, children.Count);
            if (pointer == Pointer.ATTRIBUTE || pointer == Pointer.SIMILAR_TO )
                AddSelfReflexiveNodes(root, pointer);
            else
                AddNodes(root, pointer);
            return root;
        }

        public ISenseNode FindEntailment(IWordID id)
        {
            return FindRelation(id, Pointer.ENTAILMENT);
        }

        public Dictionary<Pointer,ISenseNode> FindHolonym(IWordID id)
        {
            ISenseNode result = FindRelation(id, Pointer.HOLONYM_MEMBER);
            Dictionary<Pointer, ISenseNode> holonyms = new Dictionary<Pointer, ISenseNode>();
            if (result != null) holonyms.Add(Pointer.HOLONYM_MEMBER, result);
            
            result = FindRelation(id, Pointer.HOLONYM_PART);
            if (result != null) holonyms.Add(Pointer.HOLONYM_PART, result);
            
            result = FindRelation(id, Pointer.HOLONYM_SUBSTANCE);
            if (result != null) holonyms.Add(Pointer.HOLONYM_SUBSTANCE, result);

            return holonyms;
        }

        public Dictionary<Pointer,ISenseNode> FindHypernym(IWordID id)
        {
            ISenseNode result = FindRelation(id, Pointer.HYPERNYM);
            Dictionary<Pointer, ISenseNode> hypernyms = new Dictionary<Pointer, ISenseNode>();
            if (result != null) hypernyms.Add(Pointer.HYPERNYM, result);

            result = FindRelation(id, Pointer.HYPERNYM_INSTANCE);
            if (result != null) hypernyms.Add(Pointer.HYPERNYM_INSTANCE, result);
            
            return hypernyms;
        }

        public Dictionary<Pointer, ISenseNode> FindHyponym(IWordID id)
        {
            ISenseNode result = FindRelation(id, Pointer.HYPONYM);
            Dictionary<Pointer, ISenseNode> hyponyms = new Dictionary<Pointer, ISenseNode>();
            if (result != null) hyponyms.Add(Pointer.HYPONYM, result);
           
            result = FindRelation(id, Pointer.HYPONYM_INSTANCE);
            if (result != null) hyponyms.Add(Pointer.HYPONYM_INSTANCE, result);
            
            return hyponyms;
        }

        public Dictionary<Pointer, ISenseNode> FindMeronym(IWordID id)
        {
            ISenseNode result = FindRelation(id, Pointer.MERONYM_MEMBER);
            Dictionary<Pointer, ISenseNode> meronyms = new Dictionary<Pointer, ISenseNode>();
            if (result != null) meronyms.Add(Pointer.MERONYM_MEMBER, result);
                      
            result = FindRelation(id, Pointer.MERONYM_PART);
            if (result != null) meronyms.Add(Pointer.MERONYM_PART, result);
                        
            result = FindRelation(id, Pointer.MERONYM_SUBSTANCE);
            if (result != null) meronyms.Add(Pointer.MERONYM_SUBSTANCE,result);
           
            return meronyms ;
        }

        public ISenseNode FindSimilarTo(IWordID id)
        {
            return FindRelation(id, Pointer.SIMILAR_TO);
        }

        public ISenseNode FindAttribute(IWordID id)
        {
            return FindRelation(id, Pointer.ATTRIBUTE);
        }

        public ISenseNode FindCause(IWordID id)
        {
           return FindRelation(id, Pointer.CAUSE);
        }
        
        private void AddNodes(ISenseNode node, Pointer pointer)
        {
            Queue<ISenseNode> q = new Queue<ISenseNode>();
            q.Enqueue(node);

            while (q.Count() > 0)
            {
                var temp = q.Dequeue();
               
                var relatedSynsIds = temp.Synset.GetRelatedSynsets(pointer);
                if (relatedSynsIds == null)
                    continue;
                foreach (var synsetId in relatedSynsIds)
                {
                    ISynset s = _dictionary.GetSynset(synsetId);
                    var children = s.GetRelatedSynsets(pointer);
                    ISenseNode newNode = new SenseNode(s, children == null ? 0 : children.Count);
                    temp.Add(newNode);
                    if (children != null)
                        q.Enqueue(newNode);
                }
            }
        }
       
        private void AddSelfReflexiveNodes(ISenseNode node, Pointer pointer)
        {
            Queue<ISenseNode> q = new Queue<ISenseNode>();
            Dictionary<ISynsetID, object> visited = new Dictionary<ISynsetID, object>();
            q.Enqueue(node);
                        
            while (q.Count() > 0)
            {
                var temp = q.Dequeue();
                if(!visited.ContainsKey(temp.Synset.ID))
                    visited.Add(temp.Synset.ID, null);
                var relatedSynsIds = temp.Synset.GetRelatedSynsets(pointer);
                if (relatedSynsIds == null)
                    continue;
                foreach (var synsetId in relatedSynsIds)
                {
                    if (visited.ContainsKey(synsetId))
                        continue;
                    ISynset s = _dictionary.GetSynset(synsetId);
                    var children = s.GetRelatedSynsets(pointer);
                    ISenseNode newNode = new SenseNode(s, children == null ? 0 : children.Count);
                    temp.Add(newNode);
                    if (children != null)
                        q.Enqueue(newNode);
                }
            }
        }

        private bool IsAcceptedPOS(Pointer pointer, POS pos)
        {
            bool result = false;
            if (pointer == Pointer.ENTAILMENT || pointer == Pointer.CAUSE)
                result = pos.Tag == POS.VERB.Tag;
            else if (pointer == Pointer.SIMILAR_TO)
                result = pos.Tag == POS.ADJECTIVE.Tag;
            else if (pointer == Pointer.HYPERNYM || pointer == Pointer.HYPERNYM_INSTANCE)
                result = pos.Tag == POS.NOUN.Tag || pos.Tag == POS.VERB.Tag;
            else if(pointer == Pointer.HYPONYM || pointer == Pointer.HYPONYM_INSTANCE)
                result = pos.Tag == POS.NOUN.Tag || pos.Tag == POS.VERB.Tag;
            else if (pointer == Pointer.HOLONYM_MEMBER || 
                     pointer == Pointer.HOLONYM_PART || 
                     pointer == Pointer.HOLONYM_SUBSTANCE)
                result = pos.Tag == POS.NOUN.Tag;
            else if(pointer == Pointer.MERONYM_MEMBER ||
                    pointer == Pointer.MERONYM_PART ||
                    pointer == Pointer.MERONYM_SUBSTANCE)
                result = pos.Tag == POS.NOUN.Tag;
            else if (pointer == Pointer.ATTRIBUTE)
                result = pos.Tag == POS.NOUN.Tag || pos.Tag == POS.ADJECTIVE.Tag;
            else
                throw new ArgumentException("wrong pointer type");

            return result;
        }
        
        
    }
}
